#pragma once

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> // sockaddr_in
#include <pthread.h>

namespace client
{
    enum
    {
        USAGE_ERROR = 1,
        SOCKET_ERROR
    };

    class udpClient
    {
    public:
        udpClient(const std::string &server_ip, const uint16_t &server_port)
            : _sever_ip(server_ip), _server_port(server_port), _sockfd(-1), _quit(false)
        {
        }
        ~udpClient()
        {
        }
        void initClient()
        {
            // 1. 创建套接字
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                std::cerr << "socket error: " << errno << " : " << strerror(errno) << std::endl;
                exit(SOCKET_ERROR); // 创建套接字失败，就没必要继续了
            }
            std::cout << "socket success and sockfd: " << _sockfd << std::endl; // 测试用

            // 2. 绑定
            /*
            a. client要不要绑定ip和port？——必须要绑定。
            b. 若需要绑定，那么要不要显式的绑定？——不需要。
                因为客户端是向服务器端发送请求，所以必须要知道服务器端的端口号，所以服务器端的端口必须显式绑定。
                而客户端并没有这个需求，客户端只需要有端口即可。此外，实际中，编写服务器的是一家公司，编写客户端的是多家公司。
            如果多个来自不同公司的app都要访问一个服务器，如果app是绑定固定端口，那么就可能多个app产生冲突。
                所以，os会帮助自动绑定port，ip同理。
            c. os什么时候绑定？如何绑定？
                os在内部发送这个客户端线程没有绑定，就会随机生成一个port来绑定
            */
        }
        void run()
        {
            // 副线程——接收消息
            pthread_create(&_reader, nullptr, recvMsg, (void *)&_sockfd); // int4字节，64位机器下，指针8字节
            // 接收数据不需要知道是谁发的，因为当前是客户端，一定是服务端发送的消息。只需要知道sockfd即可

            // 主线程——发送消息
            struct sockaddr_in target;          // 发送到target
            memset(&target, 0, sizeof(target)); // 换一种写法，也可以使用bzero()
            target.sin_family = AF_INET;
            target.sin_addr.s_addr = inet_addr(_sever_ip.c_str());
            target.sin_port = htons(_server_port);

            std::string msg; // 发送的消息
            char cmdLine[1024];
            while (!_quit)
            {
                /* 为了让客户端的发送消息和接收消息解耦，即即使客户端不发送消息，也能接收服务端的消息，设计多线程，分别发收消息 */
                std::cerr << "Please Enter# ";
                // std::cin >> msg;// 输入"ls -a -l"时，cin识别的是三个字符串（空格把它分开了）
                fgets(cmdLine, sizeof(cmdLine), stdin);
                cmdLine[strlen(cmdLine) - 1] = 0;// 去除回车
                msg = cmdLine;

                // send发送消息时，os会识别到没有绑定ip和端口，就会帮我们自动绑定。
                sendto(_sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr *)&target, sizeof(target));
                // sendto(_sockfd, cmdLine, strlen(cmdLine), 0, (struct sockaddr*)&target, sizeof(target));

                // 接收服务端消息
                // char buffer[1024];
                // struct sockaddr_in server;
                // socklen_t len = sizeof(server);
                // /*
                // 虽然我们已经知道了服务端的ip和端口，但这里还是要再次接收，因为，客户端可能请求的服务器可能不止一台，当要请求的服务器
                // 不能响应时，就会自动化为我们切换另一台服务器，所以，我们要知道是哪台服务器为我们提供服务的
                // */
                // ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&server, &len);
                // if (n >= 0)
                //     buffer[n] = 0; // 换一种写法（不初始化buffer为0）
                // std::cout << "服务器端的响应：\n"
                //           << buffer << std::endl;
            }
        }

    private:
        // 类内创建线程必须是静态方法
        static void *recvMsg(void *args)
        {
            int sockfd = *(static_cast<int *>(args));
            // 分离线程，让主线程不必等待该线程
            pthread_detach(pthread_self());

            // 接收消息也应该是常驻内存的
            while (true)
            {
                // 接收服务端发来的数据
                char buffer[1024];
                struct sockaddr_in server;
                socklen_t len = sizeof(server);
                /*
                虽然我们已经知道了服务端的ip和端口，但这里还是要再次接收，因为，客户端可能请求的服务器可能不止一台，当要请求的服务器
                不能响应时，就会自动化为我们切换另一台服务器，所以，我们要知道是哪台服务器为我们提供服务的
                */
                ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&server, &len);
                if (n >= 0)
                    buffer[n] = 0; // 换一种写法（不初始化buffer为0）
                std::cout << buffer << std::endl;
            }

            return nullptr;
        }

    private:
        int _sockfd;
        std::string _sever_ip;
        uint16_t _server_port;

        pthread_t _reader;

        bool _quit; // 退出标志
    };
}